/*
 * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/compiler/optimizer/optimizations/ecma_inlining.h"

namespace ark::compiler {

AnyBaseType InlineIntrinsics::FetchInputs(CallInst *callInst)
{
    types_.clear();
    savedInputs_.clear();
    AnyBaseType type = AnyBaseType::ECMASCRIPT_INT_TYPE;
    for (auto &input : callInst->GetInputs().SubSpan(GetGraph()->GetRuntime()->GetDynamicNumFixedArgs())) {
        auto inputInst = input.GetInst();
        if (inputInst->IsSaveState()) {
            continue;
        }
        auto inputType = GetAssumedAnyType(inputInst);

        if (inputType != AnyBaseType::ECMASCRIPT_INT_TYPE) {
            type = inputType;
        }
        types_.emplace_back(inputType);
        savedInputs_.emplace_back(inputInst);
    }
    // last input is SaveState
    ASSERT(GetGraph()->GetRuntime()->GetDynamicNumFixedArgs() + types_.size() + 1 == callInst->GetInputsCount());
    if (type == AnyBaseType::UNDEFINED_TYPE) {
        type = AnyBaseType::ECMASCRIPT_DOUBLE_TYPE;
    }
    ASSERT((type == AnyBaseType::ECMASCRIPT_INT_TYPE) || (type == AnyBaseType::ECMASCRIPT_DOUBLE_TYPE));
    return type;
}

void InlineIntrinsics::ResolveInputs(AnyBaseType type, CallInst *callInst)
{
    if (type == AnyBaseType::ECMASCRIPT_DOUBLE_TYPE) {
        for (size_t i = 0; i < types_.size(); i++) {
            if (types_[i] == AnyBaseType::ECMASCRIPT_INT_TYPE) {
                auto anyCheck = GetGraph()->CreateInstAnyTypeCheck(DataType::ANY, callInst->GetPc(), savedInputs_[i], callInst->GetSaveState(), AnyBaseType::ECMASCRIPT_INT_TYPE);
                callInst->InsertBefore(anyCheck);

                auto cati = GetGraph()->CreateInstCastAnyTypeValue(callInst->GetPc(), anyCheck, AnyBaseType::ECMASCRIPT_INT_TYPE);
                callInst->InsertBefore(cati);

                auto cast = GetGraph()->CreateInstCast(AnyBaseTypeToDataType(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE), callInst->GetPc(), cati, DataType::NO_TYPE);
                callInst->InsertBefore(cast);
                savedInputs_[i] = cast;
            } else if ((types_[i] == AnyBaseType::ECMASCRIPT_DOUBLE_TYPE) || (types_[i] == AnyBaseType::UNDEFINED_TYPE)) {
                auto anyCheck = GetGraph()->CreateInstAnyTypeCheck(DataType::ANY, callInst->GetPc(), savedInputs_[i], callInst->GetSaveState(), AnyBaseType::ECMASCRIPT_DOUBLE_TYPE);
                if (savedInputs_[i]->GetOpcode() == Opcode::AnyTypeCheck) {
                    anyCheck->SetAllowedInputType(savedInputs_[i]->CastToAnyTypeCheck()->GetAllowedInputType());
                    anyCheck->SetIsIntegerWasSeen(savedInputs_[i]->CastToAnyTypeCheck()->IsIntegerWasSeen());
                }
                callInst->InsertBefore(anyCheck);

                auto cati = GetGraph()->CreateInstCastAnyTypeValue(callInst->GetPc(), anyCheck, AnyBaseType::ECMASCRIPT_DOUBLE_TYPE);
                callInst->InsertBefore(cati);
                savedInputs_[i] = cati;
            } else {
                UNREACHABLE();
            }
        }
    } else if (type == AnyBaseType::ECMASCRIPT_INT_TYPE) {
        for (size_t i = 0; i < types_.size(); i++) {
            if ((types_[i] == AnyBaseType::ECMASCRIPT_INT_TYPE) || (types_[i] == AnyBaseType::UNDEFINED_TYPE)) {
                auto anyCheck = GetGraph()->CreateInstAnyTypeCheck(DataType::ANY, callInst->GetPc(), savedInputs_[i], callInst->GetSaveState(), AnyBaseType::ECMASCRIPT_INT_TYPE);
                callInst->InsertBefore(anyCheck);

                auto cati = GetGraph()->CreateInstCastAnyTypeValue(callInst->GetPc(), anyCheck, AnyBaseType::ECMASCRIPT_INT_TYPE);
                callInst->InsertBefore(cati);
                savedInputs_[i] = cati;
            } else {
                UNREACHABLE();
            }
        }
    }
}

% Builtins.spaces.each do |space|
%   space.inlinable_methods.each do |method|
bool InlineIntrinsics::TryInline<%= space.name %><%= method.gen_cpp_id %>Gen(CallInst *callInst)
{
%       raise "Only trivial overloads are currently supported" if !method.trivial_inline_overloads?
    [[maybe_unused]] auto resolvedType = FetchInputs(callInst);

%       if method.inline_info[0].args_type.size != 0
    if (types_.size() != <%= method.inline_info[0].args_type.size %>) {
        return false;
    }
    
%           if method.get_integer_overload
    if (resolvedType == AnyBaseType::ECMASCRIPT_INT_TYPE) {
        ecmascript::EcmaInlining::BuildGuard(callInst, callInst->GetCallMethod());
        ResolveInputs(AnyBaseType::ECMASCRIPT_INT_TYPE, callInst);
        <%= method.get_integer_overload.do_inline %>
        return true;
    }
%           end
%           if method.get_double_overload
    if ((resolvedType == AnyBaseType::ECMASCRIPT_DOUBLE_TYPE) || (resolvedType == AnyBaseType::ECMASCRIPT_INT_TYPE)) {
        ecmascript::EcmaInlining::BuildGuard(callInst, callInst->GetCallMethod());
        ResolveInputs(AnyBaseType::ECMASCRIPT_DOUBLE_TYPE, callInst);
        <%= method.get_double_overload.do_inline %>
        return true;
    }
%          end
    return false;
%       else
    if (!types_.empty()) {
        return false;
    }
    ecmascript::EcmaInlining::BuildGuard(callInst, callInst->GetCallMethod());
    <%= method.get_zero_overload.do_inline -%>
    return true;
%       end
}

%   end
% end

bool InlineIntrinsics::TryInlineNativeMethodGenECMASCRIPT(CallInst *callInst)
{
    ASSERT(callInst->GetCallMethod() != nullptr);
    switch(GetGraph()->GetRuntime()->ResolveInlinableNativeMethod(callInst->GetCallMethod())) {
% Builtins.spaces.each do |space|
%   space.inlinable_methods.each do |method|
    case ark::ecmascript::EcmaRuntimeCallerId::BUILTINS_ID_<%= space.name %>_<%= method.gen_cpp_id %>:
        return TryInline<%= space.name %><%= method.gen_cpp_id %>Gen(callInst);
%   end
% end
    default:
        return false;
    }
}

}  // namespace ark::compiler
